package com.pivotal.cloud.crypto.codec;

import com.pivotal.cloud.crypto.exception.InvalidBase64CharacterException;

/**
 * @className: com.pivotal.cloud.security.crypto.codec.Base64Codec
 * @projectName: PivotalCloud项目
 * @module: PivotalCloud项目-Base64Codec类，主要位于Security安全模块-Base64Codec模块
 * @content: Base64Codec
 * @author: Powered by Marklin
 * @datetime: 2025-06-21 20:28
 * @version: 1.0.0
 * @copyright: Copyright © 2018-2025 PivotalCloud Systems Incorporated. All rights
 * reserved.
 */
public class Base64Codec {

	public static final int NO_OPTIONS = 0;

	public static final int ENCODE = 1;

	public static final int DECODE = 0;

	public static final int DO_BREAK_LINES = 8;

	public static final int URL_SAFE = 16;

	public static final int ORDERED = 32;

	private static final int MAX_LINE_LENGTH = 76;

	private static final byte EQUALS_SIGN = 61;

	private static final byte NEW_LINE = 10;

	private static final byte WHITE_SPACE_ENC = -5;

	private static final byte EQUALS_SIGN_ENC = -1;

	private static final byte[] _STANDARD_ALPHABET = new byte[] { 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
			78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108,
			109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56,
			57, 43, 47 };

	private static final byte[] _STANDARD_DECODABET = new byte[] { -9, -9, -9, -9, -9, -9, -9, -9, -9, -5, -5, -9, -9,
			-5, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -5, -9, -9, -9, -9, -9, -9, -9,
			-9, -9, -9, 62, -9, -9, -9, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -9, -9, -9, -1, -9, -9, -9, 0, 1, 2,
			3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -9, -9, -9, -9, -9, -9,
			26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -9,
			-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
			-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
			-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
			-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
			-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 };

	private static final byte[] _URL_SAFE_ALPHABET = new byte[] { 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
			78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108,
			109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56,
			57, 45, 95 };

	private static final byte[] _URL_SAFE_DECODABET = new byte[] { -9, -9, -9, -9, -9, -9, -9, -9, -9, -5, -5, -9, -9,
			-5, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -5, -9, -9, -9, -9, -9, -9, -9,
			-9, -9, -9, -9, -9, 62, -9, -9, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -9, -9, -9, -1, -9, -9, -9, 0, 1, 2,
			3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -9, -9, -9, -9, 63, -9,
			26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -9,
			-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
			-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
			-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
			-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
			-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 };

	private static final byte[] _ORDERED_ALPHABET = new byte[] { 45, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67,
			68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 95, 97, 98, 99,
			100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120,
			121, 122 };

	private static final byte[] _ORDERED_DECODABET = new byte[] { -9, -9, -9, -9, -9, -9, -9, -9, -9, -5, -5, -9, -9,
			-5, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -5, -9, -9, -9, -9, -9, -9, -9,
			-9, -9, -9, -9, -9, 0, -9, -9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -9, -9, -9, -1, -9, -9, -9, 11, 12, 13, 14,
			15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -9, -9, -9, -9, 37,
			-9, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
			-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
			-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
			-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
			-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
			-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 };

	Base64Codec() {
	}

	public static byte[] decode(byte[] bytes) {
		return decode(bytes, 0, bytes.length, 0);
	}

	public static byte[] encode(byte[] bytes) {
		return encodeBytesToBytes(bytes, 0, bytes.length, 0);
	}

	public static boolean isBase64(byte[] bytes) {
		try {
			decode(bytes);
			return true;
		}
		catch (InvalidBase64CharacterException var2) {
			return false;
		}
	}

	private static byte[] getAlphabet(int options) {
		if ((options & 16) == 16) {
			return _URL_SAFE_ALPHABET;
		}
		else {
			return (options & 32) == 32 ? _ORDERED_ALPHABET : _STANDARD_ALPHABET;
		}
	}

	private static byte[] getDecodabet(int options) {
		if ((options & 16) == 16) {
			return _URL_SAFE_DECODABET;
		}
		else {
			return (options & 32) == 32 ? _ORDERED_DECODABET : _STANDARD_DECODABET;
		}
	}

	private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes, byte[] destination, int destOffset,
			int options) {
		byte[] ALPHABET = getAlphabet(options);
		int inBuff = (numSigBytes > 0 ? source[srcOffset] << 24 >>> 8 : 0)
				| (numSigBytes > 1 ? source[srcOffset + 1] << 24 >>> 16 : 0)
				| (numSigBytes > 2 ? source[srcOffset + 2] << 24 >>> 24 : 0);
		switch (numSigBytes) {
		case 1:
			destination[destOffset] = ALPHABET[inBuff >>> 18];
			destination[destOffset + 1] = ALPHABET[inBuff >>> 12 & 63];
			destination[destOffset + 2] = 61;
			destination[destOffset + 3] = 61;
			return destination;
		case 2:
			destination[destOffset] = ALPHABET[inBuff >>> 18];
			destination[destOffset + 1] = ALPHABET[inBuff >>> 12 & 63];
			destination[destOffset + 2] = ALPHABET[inBuff >>> 6 & 63];
			destination[destOffset + 3] = 61;
			return destination;
		case 3:
			destination[destOffset] = ALPHABET[inBuff >>> 18];
			destination[destOffset + 1] = ALPHABET[inBuff >>> 12 & 63];
			destination[destOffset + 2] = ALPHABET[inBuff >>> 6 & 63];
			destination[destOffset + 3] = ALPHABET[inBuff & 63];
			return destination;
		default:
			return destination;
		}
	}

	static byte[] encodeBytesToBytes(byte[] source, int off, int len, int options) {
		if (source == null) {
			throw new NullPointerException("Cannot serialize a null array.");
		}
		else if (off < 0) {
			throw new IllegalArgumentException("Cannot have negative offset: " + off);
		}
		else if (len < 0) {
			throw new IllegalArgumentException("Cannot have length offset: " + len);
		}
		else if (off + len > source.length) {
			throw new IllegalArgumentException(String.format(
					"Cannot have offset of %d and length of %d with array of length %d", off, len, source.length));
		}
		else {
			boolean breakLines = (options & 8) > 0;
			int encLen = len / 3 * 4 + (len % 3 > 0 ? 4 : 0);
			if (breakLines) {
				encLen += encLen / 76;
			}

			byte[] outBuff = new byte[encLen];
			int d = 0;
			int e = 0;
			int len2 = len - 2;

			for (int lineLength = 0; d < len2; e += 4) {
				encode3to4(source, d + off, 3, outBuff, e, options);
				lineLength += 4;
				if (breakLines && lineLength >= 76) {
					outBuff[e + 4] = 10;
					++e;
					lineLength = 0;
				}

				d += 3;
			}

			if (d < len) {
				encode3to4(source, d + off, len - d, outBuff, e, options);
				e += 4;
			}

			if (e <= outBuff.length - 1) {
				byte[] finalOut = new byte[e];
				System.arraycopy(outBuff, 0, finalOut, 0, e);
				return finalOut;
			}
			else {
				return outBuff;
			}
		}
	}

	private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset, int options) {
		if (source == null) {
			throw new NullPointerException("Source array was null.");
		}
		else if (destination == null) {
			throw new NullPointerException("Destination array was null.");
		}
		else if (srcOffset >= 0 && srcOffset + 3 < source.length) {
			if (destOffset >= 0 && destOffset + 2 < destination.length) {
				byte[] DECODABET = getDecodabet(options);
				int outBuff;
				if (source[srcOffset + 2] == 61) {
					outBuff = (DECODABET[source[srcOffset]] & 255) << 18
							| (DECODABET[source[srcOffset + 1]] & 255) << 12;
					destination[destOffset] = (byte) (outBuff >>> 16);
					return 1;
				}
				else if (source[srcOffset + 3] == 61) {
					outBuff = (DECODABET[source[srcOffset]] & 255) << 18
							| (DECODABET[source[srcOffset + 1]] & 255) << 12
							| (DECODABET[source[srcOffset + 2]] & 255) << 6;
					destination[destOffset] = (byte) (outBuff >>> 16);
					destination[destOffset + 1] = (byte) (outBuff >>> 8);
					return 2;
				}
				else {
					outBuff = (DECODABET[source[srcOffset]] & 255) << 18
							| (DECODABET[source[srcOffset + 1]] & 255) << 12
							| (DECODABET[source[srcOffset + 2]] & 255) << 6 | DECODABET[source[srcOffset + 3]] & 255;
					destination[destOffset] = (byte) (outBuff >> 16);
					destination[destOffset + 1] = (byte) (outBuff >> 8);
					destination[destOffset + 2] = (byte) outBuff;
					return 3;
				}
			}
			else {
				throw new IllegalArgumentException(String.format(
						"Destination array with length %d cannot have offset of %d and still store three bytes.",
						destination.length, destOffset));
			}
		}
		else {
			throw new IllegalArgumentException(
					String.format("Source array with length %d cannot have offset of %d and still process four bytes.",
							source.length, srcOffset));
		}
	}

	static byte[] decode(byte[] source, int off, int len, int options) {
		if (source == null) {
			throw new NullPointerException("Cannot decode null source array.");
		}
		else if (off >= 0 && off + len <= source.length) {
			if (len == 0) {
				return new byte[0];
			}
			else if (len < 4) {
				throw new IllegalArgumentException(
						"Base64-encoded string must have at least four characters, but length specified was " + len);
			}
			else {
				byte[] DECODABET = getDecodabet(options);
				int len34 = len * 3 / 4;
				byte[] outBuff = new byte[len34];
				int outBuffPosn = 0;
				byte[] b4 = new byte[4];
				int b4Posn = 0;
				boolean i = false;

				for (int index = off; index < off + len; ++index) {
					byte sbiDecode = DECODABET[source[index] & 255];
					if (sbiDecode < -5) {
						throw new InvalidBase64CharacterException(
								String.format("Bad Base64 input character decimal %d in array position %d",
										source[index] & 255, index));
					}

					if (sbiDecode >= -1) {
						b4[b4Posn++] = source[index];
						if (b4Posn > 3) {
							outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, options);
							b4Posn = 0;
							if (source[index] == 61) {
								break;
							}
						}
					}
				}

				byte[] out = new byte[outBuffPosn];
				System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
				return out;
			}
		}
		else {
			throw new IllegalArgumentException(
					String.format("Source array with length %d cannot have offset of %d and process %d bytes.",
							source.length, off, len));
		}
	}

}
